package com.tsfyun.scm.service.impl.finance;

import cn.hutool.core.lang.Snowflake;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.tsfyun.common.base.dto.FileQTO;
import com.tsfyun.common.base.dto.TaskDTO;
import com.tsfyun.common.base.dto.TransactionFlowDTO;
import com.tsfyun.common.base.enums.DistributedLockEnum;
import com.tsfyun.common.base.enums.SerialNumberTypeEnum;
import com.tsfyun.common.base.enums.domain.DomainOprationEnum;
import com.tsfyun.common.base.enums.domain.DomainTypeEnum;
import com.tsfyun.common.base.enums.domain.RefundAccountStatusEnum;
import com.tsfyun.common.base.enums.finance.TransactionCategoryEnum;
import com.tsfyun.common.base.enums.finance.TransactionTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.dto.finance.RefundAccountDTO;
import com.tsfyun.scm.dto.finance.RefundAccountQTO;
import com.tsfyun.scm.dto.finance.RefundConfirmBankDTO;
import com.tsfyun.scm.dto.support.TaskNoticeContentDTO;
import com.tsfyun.scm.entity.customer.Customer;
import com.tsfyun.scm.entity.finance.RefundAccount;
import com.tsfyun.scm.entity.logistics.DeliveryWaybill;
import com.tsfyun.scm.entity.system.SubjectBank;
import com.tsfyun.scm.mapper.finance.RefundAccountMapper;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.customer.ICustomerService;
import com.tsfyun.scm.service.file.IUploadFileService;
import com.tsfyun.scm.service.finance.IRefundAccountService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.finance.ITransactionFlowService;
import com.tsfyun.scm.service.finance.IWriteOffService;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.ISerialNumberService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import com.tsfyun.scm.service.system.ISubjectBankService;
import com.tsfyun.scm.util.ClientInfoStatus;
import com.tsfyun.scm.util.ClientStatusMappingUtil;
import com.tsfyun.scm.util.TsfWeekendSqls;
import com.tsfyun.scm.vo.finance.RefundAccountVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * <p>
 * 退款申请 服务实现类
 * </p>
 *
 *
 * @since 2020-11-27
 */
@RefreshScope
@Service
@Slf4j
public class RefundAccountServiceImpl extends ServiceImpl<RefundAccount> implements IRefundAccountService {

    @Autowired
    private RefundAccountMapper refundAccountMapper;

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Resource
    private Snowflake snowflake;

    @Autowired
    private ISerialNumberService serialNumberService;

    @Autowired
    private IStatusHistoryService statusHistoryService;

    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;

    @Autowired
    private ICustomerService customerService;

    @Autowired
    private IWriteOffService writeOffService;

    @Autowired
    private ICommonService commonService;

    @Autowired
    private ITransactionFlowService transactionFlowService;

    @Autowired
    private IUploadFileService uploadFileService;

    @Autowired
    private ISubjectBankService subjectBankService;

    @Value("${redis.lock.time:5}")
    private Integer lockTime;

    @Override
    public PageInfo<RefundAccountVO> pageList(RefundAccountQTO qto) {
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        if(StringUtils.isNotEmpty(qto.getClientStatusId()) && ClientStatusMappingUtil.REFUND_CLIENT_STATUS_MAPPING.containsKey(qto.getClientStatusId())) {
            Map<String, ClientInfoStatus> clientStatusMapping = ClientStatusMappingUtil.REFUND_CLIENT_STATUS_MAPPING;
            qto.setStatusIds(clientStatusMapping.get(qto.getClientStatusId()).getBackStatusId());
        }
        Map<String,Object> params = beanMapper.map(qto,Map.class);
        return new PageInfo<>(refundAccountMapper.list(params));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void apply(RefundAccountDTO dto) {
        String lockKey = DistributedLockEnum.REFUND_APPLY.getCode() + ":"  + dto.getCustomerId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("退款申请未获取到锁，客户id：【%s】，操作人：【{}】", dto.getCustomerId()), SecurityUtil.getCurrentPersonName());
                throw new ServiceException("您的退款申请正在处理中，请勿重复申请");
            }
            //获取客户可退款申请金额(余额大于零)
            BigDecimal receivablesVal = transactionFlowService.getCustomerBalance(dto.getCustomerId());
            log.info("客户【{}】当前余额【{}】",dto.getCustomerId(),receivablesVal);
            TsfPreconditions.checkArgument(receivablesVal.compareTo(BigDecimal.ZERO) == 1,new ServiceException("您当前无可用退款金额"));
            if(receivablesVal.compareTo(dto.getAccountValue()) == -1){
                throw new ServiceException(String.format("您当前最大退款金额为【%s】",receivablesVal));
            }
            Customer customer = customerService.getById(dto.getCustomerId());
            RefundAccount refundAccount = new RefundAccount();
            refundAccount.setId(snowflake.nextId());
            refundAccount.setDateCreated(LocalDateTime.now());
            refundAccount.setAccountValue(dto.getAccountValue());
            refundAccount.setCustomerId(dto.getCustomerId());
            refundAccount.setPayeeBankName(dto.getPayeeBankName());
            refundAccount.setPayeeAccountName(customer.getName());
            refundAccount.setPayeeBankAccountNo(dto.getPayeeBankAccountNo());
            refundAccount.setPayeeBankAddress(dto.getPayeeBankAddress());
            refundAccount.setDocNo(serialNumberService.generateDocNo(SerialNumberTypeEnum.REFUND_ACCOUNT));
            refundAccount.setMemo(dto.getMemo());
            RefundAccountStatusEnum refundAccountStatusEnum = RefundAccountStatusEnum.WAIT_EXAMINE;
            refundAccount.setStatusId(refundAccountStatusEnum.getCode());
            super.saveNonNull(refundAccount);
            //记录状态变更
            statusHistoryService.saveHistory(DomainOprationEnum.REFUND_ADD, refundAccount.getId().toString(),RefundAccount.class.getName(),refundAccountStatusEnum.getCode(),refundAccountStatusEnum.getName());
            //写入交易流水
            TransactionFlowDTO transactionFlowDTO = new TransactionFlowDTO();
            transactionFlowDTO.setCustomerId(refundAccount.getCustomerId());
            transactionFlowDTO.setTransactionType(TransactionTypeEnum.REFUND.getCode());
            transactionFlowDTO.setTransactionCategory(TransactionCategoryEnum.OUTCOME.getCode());
            transactionFlowDTO.setTransactionNo(refundAccount.getDocNo());
            transactionFlowDTO.setAmount(refundAccount.getAccountValue());
            transactionFlowService.recordTransactionFlow(transactionFlowDTO);
            //记录退款核销
            writeOffService.writeOffRefundAccount(refundAccount);
            //发送任务通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.REFUNDACCOUNT.getCode());
            taskNoticeContentDTO.setDocumentId(refundAccount.getId().toString());
            taskNoticeContentDTO.setCustomerId(refundAccount.getCustomerId());
            taskNoticeContentDTO.setOperationCode(DomainOprationEnum.REFUND_AUDIT.getCode());
            taskNoticeContentDTO.setContent(String.format("客户【%s】提交了一份退款申请【%s】，请您尽快审核", customer.getName(),refundAccount.getDocNo()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",refundAccount.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("退款申请获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void clientApply(RefundAccountDTO dto) {
        dto.setCustomerId(SecurityUtil.getCurrentCustomerId());
        apply(dto);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void manageApply(RefundAccountDTO dto) {
        TsfPreconditions.checkArgument(Objects.nonNull(dto.getCustomerId()),new ServiceException("请选择客户"));
        apply(dto);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void approve(TaskDTO dto) {
        String lockKey = DistributedLockEnum.REFUND_APPROVE.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("退款申请商务审核未获取到锁，退款单单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            RefundAccountStatusEnum newStatus = RefundAccountStatusEnum.of(dto.getNewStatusCode());
            if(Objects.equals(newStatus,RefundAccountStatusEnum.CONFIRM_BANK)) {
                TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.REFUNDACCOUNT.getCode(),dto.getDocumentClass()),new ServiceException("操作非法"));
                RefundAccount refundAccount = commonService.changeDocumentStatus(dto);
                //发送任务通知
                TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
                taskNoticeContentDTO.setDocumentType(DomainTypeEnum.REFUNDACCOUNT.getCode());
                taskNoticeContentDTO.setDocumentId(refundAccount.getId().toString());
                taskNoticeContentDTO.setCustomerId(refundAccount.getCustomerId());
                taskNoticeContentDTO.setOperationCode(DomainOprationEnum.REFUND_CONFIRM_BANK.getCode());
                taskNoticeContentDTO.setContent(String.format("客户【%s】的退款申请【%s】已审核通过，请您尽快确认付款行。",refundAccount.getPayeeAccountName(),refundAccount.getDocNo()));
                taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",refundAccount.getDocNo()));
                taskNoticeContentService.add(taskNoticeContentDTO);
                //清空任务
                clearTaskNotice(refundAccount.getId());
            } else if (Objects.equals(newStatus,RefundAccountStatusEnum.NOT_APPROVE)) {
               //作废退款单
                toVoid(dto);
            }
        } catch (InterruptedException e) {
            log.error(String.format("退款申请商务审核获取锁异，退款单单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void toVoid(TaskDTO dto) {
        String lockKey = DistributedLockEnum.REFUND_TO_VOLD.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("退款申请作废未获取到锁，退款单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            RefundAccount refundAccount = super.getById(dto.getDocumentId());
            if(Objects.isNull(refundAccount)){
                throw new ServiceException("退款单不存在");
            }
            RefundAccountStatusEnum historyStatusEnum = RefundAccountStatusEnum.of(refundAccount.getStatusId());
            RefundAccountStatusEnum nowStatusEnum = RefundAccountStatusEnum.NOT_APPROVE;
            if(Objects.equals(nowStatusEnum,historyStatusEnum)){
                throw new ServiceException("退款单已作废");
            }
            refundAccount.setStatusId(nowStatusEnum.getCode());
            refundAccount.setApprovalInfo(dto.getMemo());
            super.updateById(refundAccount);

            //还原流水
            TransactionFlowDTO transactionFlowDTO = new TransactionFlowDTO();
            transactionFlowDTO.setCustomerId(refundAccount.getCustomerId());
            transactionFlowDTO.setTransactionType(TransactionTypeEnum.REFUND.getCode());
            transactionFlowDTO.setTransactionCategory(TransactionCategoryEnum.INCOME.getCode());
            transactionFlowDTO.setTransactionNo(refundAccount.getDocNo());
            transactionFlowDTO.setAmount(refundAccount.getAccountValue());
            transactionFlowService.recordTransactionFlow(transactionFlowDTO);
            //还原核销
            writeOffService.cancelwriteOffByRefundAccount(refundAccount);

            //记录状态变更
            statusHistoryService.saveHistory(DomainOprationEnum.REFUND_TO_VOID, refundAccount.getId().toString(),
                    RefundAccount.class.getName(),
                    historyStatusEnum.getCode(),historyStatusEnum.getName(),
                    nowStatusEnum.getCode(),nowStatusEnum.getName(),
                    dto.getMemo()
            );
            //清空任务
            clearTaskNotice(refundAccount.getId());
        } catch (InterruptedException e) {
            log.error(String.format("退款申请作废获取锁异，退款单单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAllById(Long id) {
        //已作废的数据才允许删除
        RefundAccount refundAccount = super.getById(id);
        if(Objects.isNull(refundAccount) || !Objects.equals(RefundAccountStatusEnum.NOT_APPROVE,RefundAccountStatusEnum.of(refundAccount.getStatusId()))){
            throw new ServiceException("已作废的单据才允许删除");
        }
        //删除历史状态
        statusHistoryService.removeHistory(id.toString(), DeliveryWaybill.class.getName());
        //删除文件信息
        uploadFileService.deleteByDocId(id.toString());
        //删除单据
        super.removeById(id);
        //清空任务
        clearTaskNotice(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirmBank(RefundConfirmBankDTO dto) {
        String lockKey = DistributedLockEnum.REFUND_CONFIRM_BANK.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("退款申请确认付款行未获取到锁，退款单单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.REFUNDACCOUNT.getCode(),dto.getDocumentClass()),new ServiceException("操作非法"));
            RefundAccount refundAccount = commonService.changeDocumentStatus(dto);
            RefundAccountStatusEnum newStatus = RefundAccountStatusEnum.of(dto.getNewStatusCode());
            if(Objects.equals(newStatus,RefundAccountStatusEnum.WAIT_PAY)) {
                //检查付款行是否正确
                Optional.ofNullable(dto.getPayerBankId()).orElseThrow(()->new ServiceException("请选择付款行"));
                SubjectBank subjectBank = subjectBankService.getById(dto.getPayerBankId());
                Optional.ofNullable(subjectBank).orElseThrow(()->new ServiceException("付款行不存在"));
                TsfPreconditions.checkArgument(!Objects.equals(subjectBank.getDisabled(),Boolean.TRUE),new ServiceException("您选的付款行已被禁用，请重新选择"));
                refundAccount.setPayerBankId(subjectBank.getId());
                refundAccount.setPayerBankName(subjectBank.getName());
                refundAccount.setPayerBankAccountNo(subjectBank.getAccount());
                super.updateById(refundAccount);
            } else if (Objects.equals(newStatus,RefundAccountStatusEnum.WAIT_EXAMINE)) {
                //退回商务审核(通知商务)

            }
        } catch (InterruptedException e) {
            log.error(String.format("退款申请商务审核获取锁异，退款单单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void bankReceipt(TaskDTO dto) {
        String lockKey = DistributedLockEnum.REFUND_BANK_RECEIPT.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("退款申请上传水单未获取到锁，退款单单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.REFUNDACCOUNT.getCode(),dto.getDocumentClass()),new ServiceException("操作非法"));
            RefundAccount refundAccount = commonService.changeDocumentStatus(dto);
            RefundAccountStatusEnum newStatus = RefundAccountStatusEnum.of(dto.getNewStatusCode());
            if(Objects.equals(newStatus,RefundAccountStatusEnum.COMPLETED)) {
                //检查是否上传了水单文件
                FileQTO fileQTO = new FileQTO();
                fileQTO.setDocId(refundAccount.getId().toString());
                fileQTO.setDocType("refund_account");
                fileQTO.setBusinessType("refund_receipt");
                Integer receiptCnt = uploadFileService.count(fileQTO);
                TsfPreconditions.checkArgument(receiptCnt > 0,new ServiceException("请先上传水单"));
                RefundAccount update = new RefundAccount();
                update.setAccountDate(LocalDateTime.now());
                refundAccountMapper.updateByExampleSelective(update, Example.builder(RefundAccount.class).where(
                        TsfWeekendSqls.<RefundAccount>custom().andEqualTo(false,RefundAccount::getId, refundAccount.getId())).build());
            } else if (Objects.equals(newStatus,RefundAccountStatusEnum.WAIT_EXAMINE)) {
                //退回商务审核(通知商务)

            }
        } catch (InterruptedException e) {
            log.error(String.format("退款申请上传水单获取锁异，退款单单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    public RefundAccountVO detail(Long id, String operation) {
        RefundAccount refundAccount = super.getById(id);
        Optional.ofNullable(refundAccount).orElseThrow(() -> new ServiceException("退款单数据不存在"));
        DomainStatus.getInstance().check(DomainOprationEnum.of(operation), refundAccount.getStatusId());
        return beanMapper.map(refundAccount,RefundAccountVO.class);
    }

    @Override
    public Map<String, Long> getClientRefundNum() {
        //此处把客户端客户退款状态与后台退款状态映射
        Map<String,Long> clientRefundStatusStatistic = Maps.newHashMap();
        Map<String, ClientInfoStatus> clientStatusMapping = ClientStatusMappingUtil.REFUND_CLIENT_STATUS_MAPPING;
        clientStatusMapping.forEach((k,v)->{
            clientRefundStatusStatistic.put(k,refundAccountMapper.countByCustomerAndStatusIds(SecurityUtil.getCurrentCustomerId(),v.getBackStatusId()));
        });
        return clientRefundStatusStatistic;
    }

    //清空任务通知
    public void clearTaskNotice(Long id){
        taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.REFUNDACCOUNT.getCode(), id, "");
    }
}
